using System;
using System.Collections.Generic;
using System.Text;

namespace GeneticAlgorithm
{
    /// <summary>
    /// A collection of individuals.
    /// </summary>
    /// <typeparam name="TGenotype"><see>Individual</see></typeparam>
    /// <typeparam name="TPhenotype"><see>Individual</see></typeparam>
    public abstract class Population<TGenotype, TPhenotype>
    {
        protected int maxSize;
        protected int generationCount;
        protected List<Individual<TGenotype, TPhenotype>> members;

        /// <summary>
        /// Percentage of parents that will be placed in survival pool, e.g.     
        /// 0.0 = parents live only 1 generation,
        /// 1.0 = all parents will be placed in survival pool for survival selection.
        /// Suggested value: 25%
        /// </summary>
        private float elitism = 0.25f;

        #region properties
        public int GenerationCount
        {
            get { return generationCount; }
        }

        public List<Individual<TGenotype, TPhenotype>> Members
        {
            get { return members; }
            set { members = value; }
        }

        public Individual<TGenotype, TPhenotype> MostFit
        {
            get { return members[0]; }
        }

        public Individual<TGenotype, TPhenotype> LeastFit
        {
            get { return members[members.Count - 1]; }
        }
        #endregion

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="size">max size of the population</param>
        public Population(int size, float elitism)
        {
            this.elitism = elitism;
            maxSize = size;
            generationCount = 0;

            members = new List<Individual<TGenotype, TPhenotype>>();
        }

        /// <summary>
        /// Add new generation to the population.
        /// </summary>
        /// <param name="newGeneration">a collection of individuals</param>
        public void AddGeneration(List<Individual<TGenotype, TPhenotype>> newGeneration)
        {
            // exercise elitism
            int start = (int)(members.Count * elitism);
            members.RemoveRange(start, members.Count - start);

            generationCount++;

            // update id of each offspring in the new generation
            int individualNum = 0;
            foreach (Individual<TGenotype, TPhenotype> offspring in newGeneration)
            {
                offspring.UpdateId(generationCount, individualNum++);
            }

            members.AddRange(newGeneration);

            Cull();
        }



        /// <summary>
        /// Sort the entire population by fitness, remove the expired, and the unfit.
        /// </summary>
        private void Cull()
        {
            members.Sort(new IndividualComparer());
            members.RemoveRange(maxSize, members.Count - maxSize);
        }

        /// <summary>
        /// Pick a number of unique random individuals from the population.
        /// </summary>
        /// <param name="count">number of random individuals</param>
        /// <returns>a number of random individuals</returns>
        public Individual<TGenotype, TPhenotype>[] RandomIndividuals(int count)
        {
            Individual<TGenotype, TPhenotype>[] retVal = new Individual<TGenotype, TPhenotype>[count];

            List<Individual<TGenotype, TPhenotype>> tempList = new List<Individual<TGenotype, TPhenotype>>(members);
            for (int i = 0; i < count; i++)
            {
                int index = Global.random.Next(tempList.Count);
                retVal[i] = tempList[index];
                tempList.RemoveAt(index);
            }

            return retVal;
        }

        /// <summary>
        /// Compare individual by fitness.
        /// </summary>
        class IndividualComparer : IComparer<Individual<TGenotype, TPhenotype>>
        {
            public int Compare(Individual<TGenotype, TPhenotype> a, Individual<TGenotype, TPhenotype> b)
            {
                float difference = b.Fitness - a.Fitness;
                if (difference == 0f)
                {
                    return 0;
                }
                if (difference > 0f)
                {
                    return 1;
                }
                else
                {
                    return -1;
                }

            }
        }
    }
}
